//============================================================================
// Name        : cpp_crypt.cpp
// Author      : darkflash
// Version     :
// Copyright   : Your copyright notice
// Description : Hello World in C++, Ansi-style
//============================================================================

#include <iostream>
#include <stdio.h>
#include <sys/termios.h>

#include "cryptopp/sha.h"
#include "cryptopp/aes.h"
#include "cryptopp/modes.h"
#include "cryptopp/seed.h"
#include "cryptopp/des.h"
#include "cryptopp/hex.h"
#include "cryptopp/base64.h"
using namespace std;

#ifndef STDIN_FILENO
#define STDIN_FILENO 0
#endif

#ifndef TCSANOW
#define TCSANOW 0
#endif

struct termios t;

const string AESKEY = "abcdefghijklmnopqrstuvwxyz123456"; // 32byte
const string SEEDKEY = "abcdefghijklmnop"; // 16bite
const string TRIPLEDESKEY = "abcdefghijklmnopqrstuvwx"; // 24bite

void fnOffBufferedInput()
{
	tcgetattr(STDIN_FILENO, &t); //get the current terminal I/O structure
	t.c_lflag &= ~ICANON; //Manipulate the flag bits to do what you want it to do
	tcsetattr(STDIN_FILENO, TCSANOW, &t); //Apply the new settings
}

void fnOnBufferedInput()
{
	tcgetattr(STDIN_FILENO, &t); //get the current terminal I/O structure
	t.c_lflag |= ICANON; //Manipulate the flag bits to do what you want it to do
	tcsetattr(STDIN_FILENO, TCSANOW, &t); //Apply the new settings
}

int getch(void)
{
	fnOffBufferedInput();
	int ch = getchar(); /* standard getchar call */
	fnOnBufferedInput();
	return ch; /*return received char */
}

byte* fnSha256Encrypt(const byte* decrypt, size_t decryptLen)
{
	CryptoPP::SHA256 hash;
	byte digest[CryptoPP::SHA256::DIGESTSIZE];
	hash.CalculateDigest(digest, decrypt, decryptLen);

	byte* rtn = (byte*)malloc(CryptoPP::SHA256::DIGESTSIZE);
	memcpy(rtn, digest, CryptoPP::SHA256::DIGESTSIZE);

	return rtn;
}

void fnSha256Mode()
{
	string input_text;
	cout << "input text: ";
	cin >> input_text;
	getchar();

	byte* encrypt = fnSha256Encrypt((const byte*)input_text.c_str(), input_text.length());
	string encrypt_base64;
	CryptoPP::StringSource(encrypt, CryptoPP::SHA256::DIGESTSIZE, true, new CryptoPP::Base64Encoder(new CryptoPP::StringSink(encrypt_base64)));

	cout << encrypt_base64 << endl;
}

void fnAes256Encrypt(const byte* decrypt, size_t decryptLen, const byte* key, const size_t keyLen, const byte* iv, string &encrypt)
{
	CryptoPP::AES::Encryption aesEncrypt(key, keyLen);
	CryptoPP::CBC_Mode_ExternalCipher::Encryption cbcEncrypt(aesEncrypt, iv);

	CryptoPP::StreamTransformationFilter stfEncrypt(cbcEncrypt, new CryptoPP::StringSink(encrypt), CryptoPP::BlockPaddingSchemeDef::PKCS_PADDING);
	stfEncrypt.Put(decrypt, decryptLen);
	stfEncrypt.MessageEnd();
}

void fnAes256Decrypt(const byte* encrypt, size_t encryptLen, const byte* key, const size_t keyLen, const byte* iv, string &decrypt)
{
	CryptoPP::AES::Decryption aesDecrypt(key, keyLen);
	CryptoPP::CBC_Mode_ExternalCipher::Decryption cbcDecrypt(aesDecrypt, iv);

	CryptoPP::StreamTransformationFilter stfDecrypt(cbcDecrypt, new CryptoPP::StringSink(decrypt), CryptoPP::BlockPaddingSchemeDef::PKCS_PADDING);
	stfDecrypt.Put(encrypt, encryptLen);
	stfDecrypt.MessageEnd();
}

void fnAes256Mode()
{
	string input_text;
	cout << "input text: ";
	cin >> input_text;
	getchar();

	string encrypt;
	string decrypt;

	byte iv[CryptoPP::AES::BLOCKSIZE];
	memset(iv, 0, CryptoPP::AES::BLOCKSIZE);
	fnAes256Encrypt((const byte*)input_text.c_str(), input_text.size(), (const byte*)AESKEY.c_str(), AESKEY.size(), iv, encrypt);
	fnAes256Decrypt((const byte*)encrypt.c_str(), encrypt.size(), (const byte*)AESKEY.c_str(), AESKEY.size(), iv, decrypt);

	string encrypt_base64;
	CryptoPP::StringSource(encrypt, true, new CryptoPP::Base64Encoder(new CryptoPP::StringSink(encrypt_base64)));

	cout << encrypt_base64 << endl;
	cout << decrypt << endl;
}

void fnSeed128Encrypt(const byte* decrypt, size_t decryptLen, const byte* key, const size_t keyLen, const byte* iv, string &encrypt)
{
	CryptoPP::SEED::Encryption seedEncrypt(key, keyLen);
	CryptoPP::CBC_Mode_ExternalCipher::Encryption cbcEncrypt(seedEncrypt, iv);

	CryptoPP::StreamTransformationFilter stfEncrypt(cbcEncrypt, new CryptoPP::StringSink(encrypt), CryptoPP::BlockPaddingSchemeDef::PKCS_PADDING);
	stfEncrypt.Put(decrypt, decryptLen);
	stfEncrypt.MessageEnd();
}

void fnSeed128Decrypt(const byte* encrypt, size_t encryptLen, const byte* key, const size_t keyLen, const byte* iv, string &decrypt)
{
	CryptoPP::SEED::Decryption seedDecrypt(key, keyLen);
	CryptoPP::CBC_Mode_ExternalCipher::Decryption cbcDecrypt(seedDecrypt, iv);

	CryptoPP::StreamTransformationFilter stfDecrypt(cbcDecrypt, new CryptoPP::StringSink(decrypt), CryptoPP::BlockPaddingSchemeDef::PKCS_PADDING);
	stfDecrypt.Put(encrypt, encryptLen);
	stfDecrypt.MessageEnd();
}

void fnSeed128Mode()
{
	string input_text;
	cout << "input text: ";
	cin >> input_text;
	getchar();

	string encrypt;
	string decrypt;

	byte iv[CryptoPP::SEED::BLOCKSIZE];
	memset(iv, 0, CryptoPP::SEED::BLOCKSIZE);

	fnSeed128Encrypt((const byte*)input_text.c_str(), input_text.size(), (const byte*)SEEDKEY.c_str(), SEEDKEY.size(), iv, encrypt);
	fnSeed128Decrypt((const byte*)encrypt.c_str(), encrypt.size(), (const byte*)SEEDKEY.c_str(), SEEDKEY.size(), iv, decrypt);

	string encrypt_base64;
	CryptoPP::StringSource(encrypt, true, new CryptoPP::Base64Encoder(new CryptoPP::StringSink(encrypt_base64)));

	cout << encrypt_base64 << endl;
	cout << decrypt << endl;
}

void fn3DesEncrypt(const byte* decrypt, size_t decryptLen, const byte* key, const size_t keyLen, const byte* iv, string &encrypt)
{
	CryptoPP::DES_EDE3::Encryption desEncrypt(key, keyLen);
	CryptoPP::CBC_Mode_ExternalCipher::Encryption cbcEncrypt(desEncrypt, iv);

	CryptoPP::StreamTransformationFilter stfEncrypt(cbcEncrypt, new CryptoPP::StringSink(encrypt), CryptoPP::BlockPaddingSchemeDef::PKCS_PADDING);
	stfEncrypt.Put(decrypt, decryptLen);
	stfEncrypt.MessageEnd();
}

void fn3DesDecrypt(const byte* encrypt, size_t encryptLen, const byte* key, const size_t keyLen, const byte* iv, string &decrypt)
{
	CryptoPP::DES_EDE3::Decryption seedDecrypt(key, keyLen);
	CryptoPP::CBC_Mode_ExternalCipher::Decryption cbcDecrypt(seedDecrypt, iv);

	CryptoPP::StreamTransformationFilter stfDecrypt(cbcDecrypt, new CryptoPP::StringSink(decrypt), CryptoPP::BlockPaddingSchemeDef::PKCS_PADDING);
	stfDecrypt.Put(encrypt, encryptLen);
	stfDecrypt.MessageEnd();
}

void fn3DesMode()
{
	string input_text;
	cout << "input text: ";
	cin >> input_text;
	getchar();

	string encrypt;
	string decrypt;

	byte iv[CryptoPP::DES_EDE3::BLOCKSIZE];
	memset(iv, 0, CryptoPP::DES_EDE3::BLOCKSIZE);

	fn3DesEncrypt((const byte*)input_text.c_str(), input_text.size(), (const byte*)TRIPLEDESKEY.c_str(), TRIPLEDESKEY.size(), iv, encrypt);
	fn3DesDecrypt((const byte*)encrypt.c_str(), encrypt.size(), (const byte*)TRIPLEDESKEY.c_str(), TRIPLEDESKEY.size(), iv, decrypt);

	string encrypt_base64;
	CryptoPP::StringSource(encrypt, true, new CryptoPP::Base64Encoder(new CryptoPP::StringSink(encrypt_base64)));

	cout << encrypt_base64 << endl;
	cout << decrypt << endl;
}

void fnPrintMode()
{
	cout << "1. sha256" << endl;
	cout << "2. aes256" << endl;
	cout << "3. seed128" << endl;
	cout << "4. 3des" << endl;
	cout << "q. quit" << endl;
}

int main() {
	int input_mode;

	do{
		fnPrintMode();
		input_mode = getch();
		cout << endl;
		switch(input_mode){
		case '1':
			fnSha256Mode();
			break;
		case '2':
			fnAes256Mode();
			break;
		case '3':
			fnSeed128Mode();
			break;
		case '4':
			fn3DesMode();
			break;
		}
	}while(input_mode != 'q');

	return 0;
}
